﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Data.Common;
using STS.Schema.Interfaces;
using STS.Common.Utils;
using System.ComponentModel;
using STS.Common;
using System.Globalization;
using STS.Common.Enums;

namespace STS.Schema
{
    public class SqlPropertyInfo
    {
        private object _instance;

        public string ColumnName { get; internal set; }
        public object SqlValue { get; internal set; }
        public bool HasSqlValue { get { return false; } }
        public PropertyInfo SqlObjectProperty { get; internal set; }

        private SqlPropertyInfo(string columnName)
        {
            ColumnName = columnName;
        }

        public static SqlPropertyInfo[] GetProperties(object instance, DbDataReader reader, out STSMessage[] messages)
        {
            List<SqlPropertyInfo> properties = new List<SqlPropertyInfo>();
            PropertyInfo[] infoList = instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
            List<STSMessage> msgList = new List<STSMessage>();
            for (int i = 0; i < infoList.Length; i++)
            {
                PropertyInfo info = infoList[i];
                SqlQueryPropertyAttribute sqlProp = SchemaUtils.GetAttribute<SqlQueryPropertyAttribute>(info);
                if (sqlProp != null)
                {
                    try
                    {
                        object sqlVal = reader.GetValue(reader.GetOrdinal(sqlProp.SqlQueryColumn));

                        SqlPropertyInfo sInfo = new SqlPropertyInfo(sqlProp.SqlQueryColumn) { _instance = instance, SqlObjectProperty = info, SqlValue = sqlVal };
                        sInfo.SetValue(sqlVal, sqlVal.GetType());
                        properties.Add(sInfo);
                    }
                    catch (Exception ex)
                    {
                        msgList.Add(new STSMessage() { Text = string.Format("Error reading property '{0}' of type '{1}'.", info.Name, instance.GetType().Name), Details = ex.GetBaseException().ToString(), Type = MessageType.Info });
                    }
                }
                else
                {
                    msgList.Add(new STSMessage() { Text = string.Format("Property '{0}' does not have an sql attribute.", info.Name), Type = MessageType.Info });
                }
            }
            messages = msgList.ToArray();
            return properties.ToArray();
        }

        private void SetValue(object sqlValue, Type sqlValueType)
        {
            if (SqlObjectProperty == null || _instance == null || sqlValue.Equals(DBNull.Value))
            {
                return;
            }
            //if (sqlValue.Equals(DBNull.Value))
            //{
            //    sqlValue = null;
            //    sqlValueType = typeof(Nullable);
            //}
            object destVal = sqlValue;

            if (!sqlValueType.Equals(SqlObjectProperty.PropertyType) &&
                sqlValue != null && !IsNullable(SqlObjectProperty.PropertyType) &&
                !SqlObjectProperty.PropertyType.IsEnum)
            {
                TypeConverter converter = converter = TypeDescriptor.GetConverter(sqlValueType);

                if (converter == null)
                {
                    throw new STSException(string.Format("Type converter for '{0}' not found.", SqlObjectProperty.PropertyType));
                }
                if (!converter.CanConvertTo(SqlObjectProperty.PropertyType))
                {
                    throw new STSException(string.Format("Cannot convert '{0}' to '{1}'.", sqlValue, SqlObjectProperty.PropertyType.Name));
                }

                destVal = converter.ConvertTo(sqlValue, SqlObjectProperty.PropertyType);
            }
            else if (SqlObjectProperty.PropertyType.IsEnum && Enum.IsDefined(SqlObjectProperty.PropertyType, sqlValue))
            {
                destVal = Enum.ToObject(SqlObjectProperty.PropertyType, sqlValue);
            }
            else if (sqlValue is string)
            {
                destVal = FromString(SqlObjectProperty.PropertyType, (string)sqlValue);
            }
            else if (!sqlValueType.Equals(SqlObjectProperty.PropertyType) &&
                !sqlValueType.Equals(Nullable.GetUnderlyingType(SqlObjectProperty.PropertyType)))
            {
                throw new STSException(string.Format("Cannot convert '{0}' to '{1}'.", sqlValue, SqlObjectProperty.PropertyType.Name));
            }
            SqlObjectProperty.SetValue(_instance, destVal, null);
        }

        public static object FromString(Type destType, string text)
        {
            if (IsNullable(destType))
            {
                return Convert.ChangeType(text, Nullable.GetUnderlyingType(destType), CultureInfo.InvariantCulture);
            }
            return Convert.ChangeType(text, destType, CultureInfo.InvariantCulture);
        }

        private static bool IsNullable(Type type)
        {
            return type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>));
        }
    }
}
